package com.lin.nio.channel.multiplexing;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public class WriterServer {
    public static void main(String[] args) throws IOException {

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(1122));

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        // 服务器进入死循环处理客户端的事件
        while (true) {
            selector.select();

            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                System.out.println("服务器处理事件");
                SelectionKey key = iter.next();

                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();

                    // 给连接的客户端写入大量数据
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < 300000000; i++) {
                        // Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
                        sb.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());

                    int write = sc.write(buffer);
                    System.out.println("服务端向客户端写的数据量：" + write);

                    if (buffer.hasRemaining()) { // 如果一次性没写完，给SocketChannel绑定一个可写事件，等到可以写了再来处理，不再阻塞
                        sc.register(selector,key.interestOps() + SelectionKey.OP_WRITE, buffer);
                    }

//                    while (buffer.hasRemaining()){
                        /**
                         * buffer数据量大的话，不能一次性发给客户端，会导致服务端向客户端写的数据量：0
                         *
                         * SocketChannel实际上是向底层操作系统的网络缓冲区写入数据，如果数据写入速度远远大于网络传输速度，则缓冲区就会被写满
                         * 操作系统底层socket有个缓冲区，占满了就会阻塞直到缓冲清空了才会再次发送，java层面你再怎么优化也不能突破操作系统的性能瓶颈
                         *
                         * 这样会导致服务器阻塞在这里，即使是写入的数据量是0，也会循环执行一次
                         */
//                        int write = sc.write(buffer);
//                        System.out.println("服务端向客户端写的数据量：" + write);
//                    }
                } else if(key.isWritable()){
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    SocketChannel sc = (SocketChannel) key.channel();
                    int write = sc.write(buffer);
                    System.out.println("服务端向客户端写的数据量：" + write);

                    if (!buffer.hasRemaining()) { // 写完了
                        key.attach(null); // 回收buffer
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE); // 取消可写事件
                    }
                }
            }
        }
    }
}
